﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Xml;
using System.Xml.Linq;
using GuiLabs.Undo;

namespace DynamicGeometry
{
    public abstract partial class FigureBase : 
        IFigure, 
        IPropertyGridHost, 
        IPropertyGridContentProvider,
        INotifyPropertyChanged, 
        INotifyPropertyChanging
    {
        public FigureBase()
        {
            Exists = true;
            Visible = true;
        }

        public virtual string GenerateFigureName()
        {
            return this.GenerateNewName();
        }

        private Drawing drawing;
        public Drawing Drawing
        {
            get
            {
                return drawing;
            }
            set
            {
                if (Drawing == value)
                {
                    return;
                }
                drawing = value;
            }
        }

        public virtual void OnAddingToDrawing(Drawing drawing)
        {
            if (Name == null || drawing
                .Figures
                .GetAllFiguresRecursive()
                .Where(f => f.Name == Name)
                .Where(f => f != this)
                .Any())
            {
                Name = GenerateFigureName();
            }
        }

        public virtual void OnRemovingFromDrawing(Drawing drawing)
        {
        }

        public static int ID { get; set; }

        public virtual IFigure Clone()
        {
            var newFigure = Activator.CreateInstance(this.GetType()) as IFigure;
            newFigure.Dependencies = new FigureList(this.Dependencies);
            return newFigure;
        }

        public override string ToString()
        {
            return Name;
        }

        [PropertyGridVisible]
        public string Name { get; set; }

        public virtual bool Selected { get; set; }

        private bool mEnabled = true;
        public virtual bool Enabled
        {
            get
            {
                return mEnabled;
            }
            set
            {
                mEnabled = value;
            }
        }

        [PropertyGridVisible]
        [PropertyGridName("Style")]
        public IFigureStyle StyleDisplay
        {
            get
            {
                return Style;
            }
            set
            {
                Style = value;
            }
        }

        [PropertyGridVisible]
        public virtual bool Visible { get; set; }

        [PropertyGridVisible]
        public virtual bool Locked { get; set; }

        public virtual void WriteXml(XmlWriter writer)
        {
            if (!Visible)
            {
                writer.WriteAttributeString("Visible", "false");
            }
            if (Style != null)
            {
                writer.WriteAttributeString("Style", Style.Name);
            }
        }

        public virtual void ReadXml(XElement element)
        {
            Visible = element.ReadBool("Visible", true);
            var styleAttribute = element.Attribute("Style");
            if (styleAttribute != null 
                && Drawing != null 
                && Drawing.StyleManager != null)
            {
                var style = Drawing.StyleManager[styleAttribute.Value];
                if (style != null)
                {
                    this.Style = style;
                }
            }
        }

        [PropertyGridVisible]
        public virtual void Delete()
        {
            RemoveFigureAction action = new RemoveFigureAction(Drawing, this);
            Drawing.ActionManager.RecordAction(action);
            Drawing.RaiseSelectionChanged(new Drawing.SelectionChangedEventArgs());
        }

        protected Canvas Canvas
        {
            get
            {
                return Drawing.Canvas;
            }
        }

        protected PointPair CanvasLogicalBorders
        {
            get
            {
                return ToLogical(Canvas.GetBorderRectangle());
            }
        }

        private IEnumerable<IFigure> mDependencies;
        public IEnumerable<IFigure> Dependencies
        {
            get
            {
                return mDependencies;
            }
            set
            {
                mDependencies = value;
            }
        }

        private IFigureList mDependents;
        public IFigureList Dependents
        {
            get
            {
                if (mDependents == null)
                {
                    mDependents = new FigureList();
                }
                return mDependents;
            }
            protected set
            {
                mDependents = value;
            }
        }

        public virtual int ZIndex { get; set; }

        public virtual bool Exists { get; set; }

        public Point Point(int index)
        {
            return (Dependencies.ElementAt(index) as IPoint).Coordinates;
        }

        public PointPair Line(int index)
        {
            return (Dependencies.ElementAt(index) as ILine).Coordinates;
        }

        public double Number(int index)
        {
            return (Dependencies.ElementAt(index) as INumber).Value;
        }

        IFigureStyle style;
        public IFigureStyle Style
        {
            get
            {
                return style;
            }
            set
            {
                if (style == value)
                {
                    return;
                }
                //RaisePropertyChanging("Style");
                if (style != null)
                {
                    style.PropertyChanged -= style_PropertyChanged;
                }
                style = value;
                if (style != null)
                {
                    style.PropertyChanged += style_PropertyChanged;
                    if (Drawing != null)
                    {
                        style.Drawing = Drawing;
                    }
                }
                ApplyStyle();
                //RaisePropertyChanged("Style");
            }
        }

        [PropertyGridVisible]
        [PropertyGridName("Edit style")]
        public void EditStyleButton()
        {
            if (PropertyGrid != null)
            {
                PropertyGrid.Show(this.Style, this.Drawing.ActionManager);
            }
        }

        [PropertyGridVisible]
        [PropertyGridName("Create new style")]
        public void CreateNewStyle()
        {
            Drawing.ActionManager.SetProperty(
                this, 
                "Style", 
                Drawing.StyleManager.CreateNewStyle(this));
            EditStyleButton();
        }

        void style_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ApplyStyle();
        }

        public void EnsureStyleAssigned()
        {
            if (Style == null && Drawing != null)
            {
                Style = Drawing.StyleManager.AssignDefaultStyle(this);
            }
            else if (Style != null)
            {
                Style.Drawing = Drawing;
            }
        }

        public virtual void OnAddingToCanvas(Canvas newContainer)
        {
            Canvas.SetZIndex(newContainer, this.ZIndex);
            EnsureStyleAssigned();
        }

        public abstract void ApplyStyle();

        public virtual void OnRemovingFromCanvas(Canvas leavingContainer)
        {
        }

        public virtual void UpdateExistence()
        {
            if (!Dependencies.IsEmpty())
            {
                Exists = Dependencies.All(f => f.Exists);
            }
            else
            {
                Exists = true;
            }
        }

        public virtual void Recalculate() { }

        /// <summary>
        /// Takes Coordinates or whatever other location information is current for the figure
        /// and updates the shape or other visual representation with these coordinates
        /// </summary>
        /// <example>
        /// Usually means updating the Shape like this:
        /// Shape.MoveTo(Coordinates.ToPhysical());
        /// </example>
        public virtual void UpdateVisual() { }

        public abstract IFigure HitTest(Point point);

        public bool Equals(IFigure other)
        {
            return object.ReferenceEquals(this, other);
        }

        #region Coordinates

        protected double CursorTolerance
        {
            get
            {
                return Drawing.CoordinateSystem.CursorTolerance;
            }
        }

        protected double ToPhysical(double logicalLength)
        {
            return Drawing.CoordinateSystem.ToPhysical(logicalLength);
        }

        protected Point ToPhysical(Point point)
        {
            return Drawing.CoordinateSystem.ToPhysical(point);
        }

        protected PointPair ToPhysical(PointPair pointPair)
        {
            return Drawing.CoordinateSystem.ToPhysical(pointPair);
        }

        protected double ToLogical(double pixelLength)
        {
            return Drawing.CoordinateSystem.ToLogical(pixelLength);
        }

        protected Point ToLogical(Point pixel)
        {
            return Drawing.CoordinateSystem.ToLogical(pixel);
        }

        protected PointPair ToLogical(PointPair pointPair)
        {
            return Drawing.CoordinateSystem.ToLogical(pointPair);
        }

        #endregion

        public PropertyGrid PropertyGrid { get; set; }

        public virtual object GetContentForPropertyGrid()
        {
            return this;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanging;
        protected void RaisePropertyChanging(string propertyName)
        {
            if (PropertyChanging != null)
            {
                PropertyChanging(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}